#pragma once

#include "util.hpp"
#include <memory>
#include <fstream>
#include <sstream>
#include <cassert>




namespace lcllog
{
    class LogOut
    {
    public:
        using ptr = std::shared_ptr<LogOut>;
        LogOut() {}
        ~LogOut() {}
        virtual void log(const char* data, size_t len) = 0;
    };

    class StdoutOut : public LogOut
    {
    public:
        void log(const char* data, size_t len) override
        {
            std::cout.write(data, len);
        }
    };

    class FileOut : public LogOut
    {
    public:
        FileOut(const std::string& pathname)
        : _pathname(pathname)
        {
            // 创建文件日志所在的目录
            util::file::create_directory(util::file::path(_pathname));
            // 创建并打开日志文件 以二进制追加模式打开文件
            _ofs.open(_pathname, std::ios::binary | std::ios::app);
            assert(_ofs.is_open()); 
        }
        void log(const char* data, size_t len) override
        {
            _ofs.write(data, len);
            if(_ofs.good() == false)
                std::cout << "日志输出文件失败！" << std::endl;
        }
    private:
        std::string _pathname;
        std::ofstream _ofs;
    };
    // 滚动文件输出(以大小进行滚动，一个文件内容达到1G时，重新创建新文件进行写入)
    class RollOut : public LogOut
    {
    public:
        RollOut(const std::string& basename, size_t max_size)
        : _basename(basename)
        , _max_size(max_size)
        , _cur_size(0)
        , _name_count(0)
        {
            std::string pathname = createFilename();
            // 创建文件日志所在的目录
            util::file::create_directory(util::file::path(pathname));
            // 创建并打开日志文件 以二进制追加模式打开文件
            _ofs.open(pathname, std::ios::binary | std::ios::app);
            assert(_ofs.is_open()); 
        }
        // 将日志写入到文件中，写入前判断文件大小，超过最大值时就切换新文件进行写入
        void log(const char* data, size_t len) override
        {
            if(_cur_size >= _max_size)
            {
                _ofs.close(); // 关闭原来打开的文件
                // 创建新的文件并打开，在新文件中写入
                std::string newpathname = createFilename();
                _ofs.open(newpathname, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
                _cur_size = 0;
            }
            _ofs.write(data, len);
            if (_ofs.good() == false) {
                std::cout << "日志输出文件失败！\n";
            }
            _cur_size += len;
        }
    private:
        // 以时间来构造文件名
        std::string createFilename()
        {
            time_t t = util::Date::now(); // 获取当前时间戳
            struct tm local_time;
            localtime_r(&t, &local_time);

            std::stringstream filename;
            filename << _basename;
            filename << local_time.tm_year + 1900;
            filename << local_time.tm_mon + 1;
            filename << local_time.tm_mday;
            filename << local_time.tm_hour;
            filename << local_time.tm_min;
            filename << local_time.tm_sec;
            filename << "-";
            filename << _name_count++;
            filename << ".log";
            return filename.str();
        }
    private:
        //通过基础文件名 +扩展文件名(以时间生成)组成一个实际的当前输出文件名
        size_t _name_count; // 用来防止速度太快，在一秒内创建很多文件，那样名字就重复了
        std::string _basename; // ./logs/base-  --> ./logs/bas-20250405114421.log
        std::ofstream _ofs;
        size_t _max_size; // 单个日志文件最大尺寸
        size_t _cur_size; // 当前日志文件已写入大小
    };

 
    class OutFactory
    {
    public:
        template<class LogType, class ...Args>
        static LogOut::ptr create(Args&& ...args)
        {
            return std::make_shared<LogType>(std::forward<Args>(args)...);
        }
    };
}

/*扩展一个以时间作为文件滚动切换类型的日志落地模块
    1.以时间进行文件滚动，实际上是以时间段进行滚动
    实现思想：以当前系统时间，取模时间段大小，可以得到当前时间段是第几个时间段
    每次以当前系统时间取模，判断与当前文件的时间段是否一致，不一致代表不是同一个时间段
*/
enum class TimeGap
{
    GAP_MINUTE,
    GAP_HOUR,
    GAP_DAY,
};
class RollByTimeOut : public lcllog::LogOut
{
    public:
        RollByTimeOut(const std::string& basename, TimeGap gap_type)
        : _basename(basename)
        {
            switch (gap_type)
            {
                case TimeGap::GAP_MINUTE: _gap_size = 60; break;
                case TimeGap::GAP_HOUR: _gap_size = 3600; break;
                case TimeGap::GAP_DAY: _gap_size = 3600 * 24; break;
            }
            _cur_gap = lcllog::util::Date::now() / _gap_size; // 计算当前的时间段
            std::string filename = createFilename();
            // 创建文件日志所在的目录
            lcllog::util::file::create_directory(lcllog::util::file::path(filename));
            // 创建并打开日志文件 以二进制追加模式打开文件
            _ofs.open(filename, std::ios::binary | std::ios::app);
            assert(_ofs.is_open()); // is_open()检查文件是否成功打开
        }
        void log(const char* data, size_t len) override
        {
            // 判断当前时间是否是当前文件的时间段，不是则切换文件
            time_t curTime = lcllog::util::Date::now();
            if((curTime / _gap_size) != _cur_gap)
            {
                _ofs.close();
                std::string filename = createFilename();
                _cur_gap = curTime / _gap_size;
                _ofs.open(filename, std::ios::binary | std::ios::app);
                assert(_ofs.is_open()); // is_open()检查文件是否成功打开
            }
            _ofs.write(data, len);
            if(_ofs.good() == false)
                std::cout << "日志输出文件失败！" << std::endl;
        }
    private:
        std::string createFilename()
        {
            time_t t = lcllog::util::Date::now(); // 获取当前时间戳
            struct tm local_time;
            localtime_r(&t, &local_time);

            std::stringstream filename;
            filename << _basename;
            filename << local_time.tm_year + 1900;
            filename << local_time.tm_mon + 1;
            filename << local_time.tm_mday;
            filename << local_time.tm_hour;
            filename << local_time.tm_min;
            filename << local_time.tm_sec;
            filename << ".log";
            return filename.str();
        }
    private:
        std::string _basename;
        std::ofstream _ofs;
        size_t _cur_gap; // 当前是第几个时间段
        size_t _gap_size; // 时间段的大小
};